package com.onesports.intelligent.k12.polarlight.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.onesports.intelligent.k12.polarlight.cache.CustomCacheKeyGenerator;
import com.onesports.intelligent.k12.polarlight.cache.CacheBatchEvictResolver;
import com.onesports.intelligent.k12.polarlight.cache.TokenEvictCacheResolver;
import com.onesports.intelligent.k12.polarlight.serializer.SimpleGrantedAuthorityDeserializer;
import io.github.jhipster.config.JHipsterProperties;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.redisson.jcache.configuration.RedissonConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import java.io.Serializable;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.TimeUnit;


/**
 * @author aj
 */
@Configuration
@EnableCaching
public class CacheConfiguration {

    @Bean
    public javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration(JHipsterProperties jHipsterProperties, ApplicationProperties applicationProperties) {
        MutableConfiguration<Object, Object> jcacheConfig = new MutableConfiguration<>();
        jcacheConfig.setStatisticsEnabled(true);
        jcacheConfig.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new javax.cache.expiry.Duration(TimeUnit.SECONDS, jHipsterProperties.getCache().getRedis().getExpiration())));
        return RedissonConfiguration.fromInstance(redissonClient(jHipsterProperties, applicationProperties), jcacheConfig);
    }

    @Bean
    public RedissonClient redissonClient(JHipsterProperties jHipsterProperties, ApplicationProperties applicationProperties) {
        URI redisUri = URI.create(jHipsterProperties.getCache().getRedis().getServer()[0]);
        Config config = new Config();
        if (applicationProperties.getRedis().getLock() != null
                && applicationProperties.getRedis().getLock().getLockWatchdogTimeout() > 0) {
            config.setLockWatchdogTimeout(applicationProperties.getRedis().getLock().getLockWatchdogTimeout());
        }

        if (jHipsterProperties.getCache().getRedis().isCluster()) {
            ClusterServersConfig clusterServersConfig = config
                    .useClusterServers()
                    .setMasterConnectionPoolSize(jHipsterProperties.getCache().getRedis().getConnectionPoolSize())
                    .setMasterConnectionMinimumIdleSize(jHipsterProperties.getCache().getRedis().getConnectionMinimumIdleSize())
                    .setSubscriptionConnectionPoolSize(jHipsterProperties.getCache().getRedis().getSubscriptionConnectionPoolSize())
                    .addNodeAddress(jHipsterProperties.getCache().getRedis().getServer());

            if (redisUri.getUserInfo() != null) {
                clusterServersConfig.setPassword(redisUri.getUserInfo().substring(redisUri.getUserInfo().indexOf(':') + 1));
            }
        } else {
            SingleServerConfig singleServerConfig = config
                    .useSingleServer()
                    .setSubscriptionsPerConnection(5000)
                    .setConnectTimeout(30000)
                    .setConnectionPoolSize(jHipsterProperties.getCache().getRedis().getConnectionPoolSize())
                    .setConnectionMinimumIdleSize(jHipsterProperties.getCache().getRedis().getConnectionMinimumIdleSize())
                    .setSubscriptionConnectionPoolSize(jHipsterProperties.getCache().getRedis().getSubscriptionConnectionPoolSize())
                    .setSubscriptionConnectionMinimumIdleSize(jHipsterProperties.getCache().getRedis().getSubscriptionConnectionMinimumIdleSize())
                    .setAddress(jHipsterProperties.getCache().getRedis().getServer()[0])
                    .setPassword(applicationProperties.getRedis().getPassword()).setDatabase(applicationProperties.getRedis().getDb());
            if (redisUri.getUserInfo() != null) {
                singleServerConfig.setPassword(redisUri.getUserInfo().substring(redisUri.getUserInfo().indexOf(':') + 1));
            }
        }
        return Redisson.create(config);
    }

    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //默认缓存不过期
                .entryTtl(Duration.ZERO)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(buildSerializer()))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

    @Bean
    public CacheResolver customCacheResolver(LettuceConnectionFactory lettuceConnectionFactory) {
        return new CacheBatchEvictResolver(cacheManager(lettuceConnectionFactory));
    }

    @Bean
    public CacheResolver tokenEvictCacheResolver(LettuceConnectionFactory lettuceConnectionFactory) {
        return new TokenEvictCacheResolver(cacheManager(lettuceConnectionFactory));
    }


    /**
     * RedisTemplate配置
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(
            LettuceConnectionFactory connectionFactory) {
        connectionFactory.setShareNativeConnection(false);
        return buildTemplate(connectionFactory);
    }

    private Jackson2JsonRedisSerializer buildSerializer() {
        // 设置序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper()
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule());

        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addDeserializer(
                SimpleGrantedAuthority.class, new SimpleGrantedAuthorityDeserializer());
        om.registerModule(simpleModule);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

    private RedisTemplate<String, Serializable> buildTemplate(LettuceConnectionFactory connectionFactory) {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = buildSerializer();
        // 配置redisTemplate
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(connectionFactory);
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        // key序列化
        redisTemplate.setKeySerializer(stringSerializer);
        // value序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // Hash key序列化
        redisTemplate.setHashKeySerializer(stringSerializer);
        // Hash value序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 实例化 ValueOperations 对象,可以使用 String 操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Serializable> valueOperations(RedisTemplate<String, Serializable> redisTemplate) {

        return redisTemplate.opsForValue();
    }

    @Bean
    public HashOperations<String, Serializable, Serializable> hashOperations(RedisTemplate<String, Serializable> redisTemplate) {

        return redisTemplate.opsForHash();
    }

    @Bean
    public ListOperations<String, Serializable> listOperations(RedisTemplate<String, Serializable> redisTemplate) {

        return redisTemplate.opsForList();
    }

    @Bean
    public CustomCacheKeyGenerator customCacheKeyGenerator() {
        return new CustomCacheKeyGenerator();
    }

}
